﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Defines the duration of an animation.
	/// </summary>
	[TypeConverter(typeof(DurationConverter))]
	public struct Duration : IEquatable<Duration>, IComplexProperty {


		#region Static Readonly Fields: Standard values

		/// <summary>
		/// Instructs jQuery UI not to animate the action taken.
		/// </summary>
		public static readonly Duration Immediate = new Duration();

		/// <summary>
		/// Instructs jQuery UI to use the duration associated with the predefined "normal" animaiton speed.
		/// </summary>
		public static readonly Duration Normal = new Duration(JQuery.Speed.Normal);

		/// <summary>
		/// Instructs jQuery UI to use the duration associated with the predefined "fast" animaiton speed.
		/// </summary>
		public static readonly Duration Fast = new Duration(JQuery.Speed.Fast);

		/// <summary>
		/// Instructs jQuery UI to use the duration associated with the predefined "slow" animaiton speed.
		/// </summary>
		public static readonly Duration Slow = new Duration(JQuery.Speed.Slow);

		#endregion


		#region Members

		private readonly Speed? speed;
		private readonly int? milliseconds;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new GreenIsomer.Web.Controls.JQuery.Duration value using the value associated with a predefined animation speed.
		/// </summary>
		/// <param name="speed">The associated predefined animation speed to use.</param>
		/// <exception cref="ArgumentException">Speed value not defined.</exception>
		public Duration(Speed speed) {
			if(!Enum.IsDefined(typeof(Speed), speed)) throw new ArgumentException("Value not defined.", "speed");

			this.speed = speed;
			milliseconds = null;
		}

		/// <summary>
		/// Initializes a new GreenIsomer.Web.Controls.JQuery.Duration value using milliseconds.
		/// </summary>
		/// <param name="milliseconds">The fixed number of milliseconds the animation should span.</param>
		/// <exception cref="ArgumentOutOfRangeException">Milliseconds value must be non-negative.</exception>
		public Duration(int milliseconds) {
			if(milliseconds < 0) throw new ArgumentOutOfRangeException("milliseconds", "Value must be non-negative.");

			speed = null;
			this.milliseconds = milliseconds;
		}

		#endregion


		#region Properties

		/// <summary>
		/// If applicable, the associated predefined animation speed to use.
		/// </summary>
		public Speed? Speed {
			get { return speed; }
		}

		/// <summary>
		/// If applicable, the fixed number of milliseconds the animation should span.
		/// </summary>
		public int? Milliseconds {
			get { return milliseconds; }
		}

		/// <summary>
		/// Whether or not a predefined speed or fixed duration has been assigned.
		/// </summary>
		public bool HasValue {
			get { return speed.HasValue || milliseconds.HasValue; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current GreenIsomer.Web.Controls.JQuery.Duration value.
		/// </summary>
		/// <param name="obj">The System.Object to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is Duration) return this == (Duration)obj;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified Duration is equal to the current GreenIsomer.Web.Controls.JQuery.Duration value.
		/// </summary>
		/// <param name="other">The GreenIsomer.Web.Controls.JQuery.Duration to compare.</param>
		/// <returns>Whether or not the provided Duration value is equal.</returns>
		public bool Equals(Duration other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for GreenIsomer.Web.Controls.JQuery.Duration.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			if(milliseconds.HasValue)
				return milliseconds.Value;
			else if(speed.HasValue)
				return ~(int)speed.Value; // Normal: -1, Slow: -2, Fast: -3
			else
				return int.MinValue;
		}

		/// <summary>
		/// Returns a System.String that represents the current GreenIsomer.Web.Controls.JQuery.Duration value.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {
			if(speed.HasValue) return speed.Value.ToString();
			if(milliseconds.HasValue) return milliseconds.Value.ToString(CultureInfo.InvariantCulture);
			return string.Empty;
		}

		#endregion


		#region Operators

		/// <summary>
		/// Compares the equality of two GreenIsomer.Web.Controls.JQuery.Duration values.
		/// </summary>
		/// <param name="value0">The first Duration value.</param>
		/// <param name="value1">The second Duration value.</param>
		/// <returns>Whether or not the two Duration values are equal.</returns>
		public static bool operator ==(Duration value0, Duration value1) {
			if(!value0.HasValue && !value1.HasValue) return true;
			if(value0.speed.HasValue && value1.speed.HasValue) return value0.speed.Value == value1.speed.Value;
			if(value0.milliseconds.HasValue && value1.milliseconds.HasValue) return value0.milliseconds.Value == value1.milliseconds.Value;
			return false;
		}

		/// <summary>
		/// Compares the inequality of two GreenIsomer.Web.Controls.JQuery.Duration values.
		/// </summary>
		/// <param name="value0">The first Duration value.</param>
		/// <param name="value1">The second Duration value.</param>
		/// <returns>Whether or not the two Duration values are distinct.</returns>
		public static bool operator !=(Duration value0, Duration value1) {
			if(!value0.HasValue && !value1.HasValue) return false;
			if(value0.speed.HasValue && value1.speed.HasValue) return value0.speed.Value != value1.speed.Value;
			if(value0.milliseconds.HasValue && value1.milliseconds.HasValue) return value0.milliseconds.Value != value1.milliseconds.Value;
			return true;
		}

		#endregion


		#region Explicit implementation: IPropertyValue

		void IComplexProperty.AddPropertyValue(PropertyCollection properties, string name) {
			if(speed.HasValue) properties.AddEnumValue(name, speed.Value);
			else if(milliseconds.HasValue) properties.AddValue(name, milliseconds.Value);
			else properties.AddNullValue(name);
		}

		#endregion


	}

}